#include "samples_preprocessor.h"

#include "audio_formats.h"
#include "codec/pcm_codec.h"
#include "codec/alaw_codec.h"
#include "codec/ulaw_codec.h"
#include "logger.h"

#include "ui_socket.h"

const std::string CSamplesPreprocessor::sIniSection = "SAMPLES_PREPROCESSOR";

CSamplesPreprocessor::CSamplesPreprocessor
(
		NAudioFormats::SStreamInfo pIoStreamInfo,
		CStegCallHandle* pCallHandle,
		unsigned int pByteBufferSize,
		CPropertiesReader* pReader
) :
		mError(true),
		mReader(pReader),
		mUseExtractThread(true),
		mOverwrite(false),
		mWave(NULL),
		mConvertCodec(NULL),
		mExtractThread(pByteBufferSize, pCallHandle->getEmbeddingEngine()->getNumRequiredSamples() * getBytePerSample(pIoStreamInfo.mSamplesWidth)),
		mCallHandle(pCallHandle),
		mEmbeddingEngine(pCallHandle->getEmbeddingEngine()),
		mIoStreamInfo(pIoStreamInfo),
		mCodec(newCodecPerCodecInfo(mIoStreamInfo.mCodec)),
		mDoubleSampleBuf(NULL),
		mDoubleSampleCount(0),
		mConvertDoubleSampleBuf(NULL),
		mConvertDoubleSampleCount(0),
		m16BitSampleBuf(NULL),
		m16BitSampleCount(0),
		mBytePerSample(getBytePerSample(mIoStreamInfo.mSamplesWidth)),
		mExitForced(false),
		mAmpEmbed(0.0),
		mAmpEmbedCount(0),
		mAmpExtract(0.0),
		mAmpExtractCount(0)
{
	if (mReader) {
		std::string lStrValue = "";
		if(mReader->containsKey(sIniSection, "USE_EXTRACT_THREAD")
		&& mReader->getValue(sIniSection, "USE_EXTRACT_THREAD", lStrValue)) {
			if (lStrValue == "false" || lStrValue == "False") {
				printl(0, __FILE__, "deactivate extract thread");
				mUseExtractThread = false;
			}
		}
		if(mReader->containsKey(sIniSection, "OVERWRITE")
		&& mReader->getValue(sIniSection, "OVERWRITE", lStrValue)) {
			if (lStrValue == "true" || lStrValue == "True") {
				printl(0, __FILE__, "activate overwriting");
				mOverwrite = true;
			}
		}
		if (mOverwrite) {
			if(mReader->containsKey(sIniSection, "WAV_FILE")
			&& mReader->getValue(sIniSection, "WAV_FILE", lStrValue)) {
				mWave = new CWaveIO(ISampleIO::EMBED, lStrValue, true);
				if (mWave->init(mReader) == -1) {
					printl(LL_ERR, __FILE__, "can't init wave file for overwriting");
					mOverwrite = false;
				}
				else {
					if (mIoStreamInfo.mSampleRate != mWave->getStreamInfo().mSampleRate) {
						printl(LL_ERR, __FILE__, "wave file for overwriting has wrong sample rate");
						mOverwrite = false;
					}
					//if (mIoStreamInfo.mCodec != mWave->getStreamInfo().mCodec) {
						mConvertCodec = newCodecPerCodecInfo(mWave->getStreamInfo().mCodec);
						printl(0, __FILE__, "activate codec convertion for overwriting");
					//}
				}
			}
		}
	}

	if (mUseExtractThread) {
		printl(0, __FILE__, "Created extract thread");
		if (pthread_create(&mExtractThread.mThreadId, NULL, extractThread, this)) {
			printl(LL_ERR, __FILE__, "No thread can be created for extract");
			return;
		}
	}

	printl(0, __FILE__, "overwriting is %s", mOverwrite ? "active" : "inactive");
	mError = false;
}

CSamplesPreprocessor::~CSamplesPreprocessor()
{
	if (mUseExtractThread) {
		printl(0, __FILE__, "Sending exit signal and waiting for thread");

		pthread_mutex_lock(&mExtractThread.mSync);
		mExtractThread.mExit = mExitForced ? CExtractThread::EXIT_STATE_FORCED : CExtractThread::EXIT_STATE_NORMAL;
		pthread_cond_signal(&mExtractThread.mAvail);
		pthread_mutex_unlock(&mExtractThread.mSync);

		pthread_join(mExtractThread.mThreadId, NULL);

		printl(0, __FILE__, "Thread done");
	}

	if(mCodec) {
		delete mCodec;
		mCodec = NULL;
	}
	if(mConvertCodec) {
		delete mConvertCodec;
		mConvertCodec = NULL;
	}
	get16BitSampleBuffer(0); // free the intern buffer for 16 bit samples
	getDoubleBuffer(0); // free the intern buffer for double samples
	getConvertDoubleBuffer(0); // free the intern buffer for double samples
	if (mWave) {
		mWave->destroy();
		delete mWave;
		mWave = NULL;
	}
}

int CSamplesPreprocessor::createStegoObject(void* pStegoObject, unsigned int pStegoObjectLength, unsigned int pLeak)
{
	if(!mBytePerSample) {
		return -1;
	}
	if(pStegoObjectLength % mBytePerSample) {
		printl(LL_ERR, __FILE__, "IO data width is not a multiple of %u byte.\n",
				mBytePerSample);
		return -1;
	}
	if(!mEmbeddingEngine) {
		printl(LL_ERR, __FILE__, "No embedding engine available.\n");
		return -1;
	}
	unsigned int lByteCountForEmbedding = mEmbeddingEngine->getNumRequiredSamples() * mBytePerSample;
	if(lByteCountForEmbedding == 0) {
		printl(LL_ERR, __FILE__, "getNumRequiredSamples() doesn't return > 0\n");
		return -1;
	}
	if(pStegoObjectLength % lByteCountForEmbedding) {
		printl(LL_ERR, __FILE__, "%d (byte) is not a multiple of %d (byte)\n",
				pStegoObjectLength,
				lByteCountForEmbedding);
		return -1;
	}

	unsigned char* lStegoBuffer(static_cast<unsigned char*>(pStegoObject));
	if (mOverwrite) {
		void* lConvertSampleBuffer(NULL);
		int lSampleCount(pStegoObjectLength / mBytePerSample);
		ISampleIO::EAction lAction;
		NAudioFormats::SStreamInfo lSi(mWave->getStreamInfo());
		unsigned int lConvertBytePerSample(getBytePerSample(lSi.mSamplesWidth));
		unsigned int lConvertLength(lSampleCount * lConvertBytePerSample);
		if (mWave->read(&lConvertSampleBuffer, lAction, 1000, lConvertLength) ==
				(int)lConvertLength) {
			double* lDoubleSamples = getConvertDoubleBuffer(lSampleCount);
			convertFromIntToDouble(lConvertSampleBuffer, lDoubleSamples,
					lSampleCount, lSi, mConvertCodec);
			convertFromDoubleToInt(lDoubleSamples, lStegoBuffer,
					lSampleCount, mIoStreamInfo, mCodec);
		}
		else {
			printl(LL_ERR, __FILE__, "can't overwrite samples");
		}
	}

	for (unsigned int i = 0; i < (pStegoObjectLength / lByteCountForEmbedding); i++) {
		int rv = embedOrExtract(lStegoBuffer, lByteCountForEmbedding/mBytePerSample, pLeak/mBytePerSample, 0, true);
		pLeak = 0;

		if (rv < 0) {
			return -1;
		}

		if (rv == 0) {
			return 0;
		}

		lStegoBuffer += lByteCountForEmbedding;
	}
	return 1;
}

int CSamplesPreprocessor::extractFromStegoObject(void* pStegoObject, unsigned int pStegoObjectLength, unsigned int pLeak)
{
	if (mError) {
		printl(LL_ERR, __FILE__, "No thread for extract exists");
		return -1;
	}
	if(!mBytePerSample) {
		return -1;
	}

	if(pStegoObjectLength % mBytePerSample) {
		printl(LL_ERR, __FILE__, "IO data width is not a multiple of %u byte.\n",
				mBytePerSample);
		return -1;
	}

	if (mUseExtractThread) {
		// EXTRA THREAD VERSION
		pthread_mutex_lock(&mExtractThread.mSync);
		int lByteDiff(static_cast<int>(pLeak + pStegoObjectLength) -
				static_cast<int>(mExtractThread.mRingBuffer.size() - mExtractThread.mRingBuffer.len()));
		if (lByteDiff > 0) {
			printl(LL_WARN, __FILE__, "Dropping %d bytes from ring buffer", lByteDiff);
		}

		if (pLeak > 0) {
			mExtractThread.mRingBuffer.put(NULL, pLeak); // fill with zeros
		}

		mExtractThread.mRingBuffer.put(pStegoObject, pStegoObjectLength);

		mExtractThread.mSendInfoCount += pStegoObjectLength;
		if (mExtractThread.mSendInfoCount >= MAX_SEND_INFO_COUNT) {
			mExtractThread.mSendInfoCount = 0;
			CUISocket::getInstance()->sendExtractBufferInfo(mExtractThread.mRingBuffer.len(), mExtractThread.mRingBuffer.size());
		}

		pthread_cond_signal(&mExtractThread.mAvail);
		pthread_mutex_unlock(&mExtractThread.mSync);
	}
	else {
		// ANTI THREAD VERSION
		unsigned int lByteCountForEmbedding = mEmbeddingEngine->getNumRequiredSamples() * mBytePerSample;
		if(lByteCountForEmbedding == 0) {
			printl(LL_ERR, __FILE__, "getNumRequiredSamples() doesn't return > 0\n");
			return -1;
		}
		if(pStegoObjectLength % lByteCountForEmbedding) {
			printl(LL_ERR, __FILE__, "%d (byte) is not a multiple of %d (byte)\n",
					pStegoObjectLength,
					lByteCountForEmbedding);
			return -1;
		}

		unsigned char* lStegoBuffer(static_cast<unsigned char*>(pStegoObject));

		for (unsigned int i = 0; i < (pStegoObjectLength / lByteCountForEmbedding); i++) {
			int rv = embedOrExtract(lStegoBuffer, lByteCountForEmbedding/mBytePerSample, pLeak/mBytePerSample, 0, false);
			pLeak = 0;

			if (rv < 0) {
				return -1;
			}
			if (rv > 0) {
				printl(LL_WARN, __FILE__, "skip of samples for next call not supported: skip: %d %u %u/%u",
						rv,
						lByteCountForEmbedding/mBytePerSample,
						lByteCountForEmbedding,
						mBytePerSample);
			}

			lStegoBuffer += lByteCountForEmbedding;
		}
	}

	return 0;
}

unsigned int CSamplesPreprocessor::convertFromIntToDouble
(
		void* pSamplesIn,
		double* pSamplesOut,
		unsigned int pSampleLength,
		NAudioFormats::SStreamInfo &pIoStreamInfo,
		ICodec* pCodec
)
{
	unsigned int lBytePerSample = getBytePerSample(pIoStreamInfo.mSamplesWidth);

	if(!pSamplesIn || !pSamplesOut || !pCodec || !lBytePerSample) {
		return 0;
	}
	if(pIoStreamInfo.mCodec != pCodec->getCodecType()) {
		printl(LL_ERR, __FILE__, "convertFromIntToDouble: wrong codec.\n");
		return 0;
	}
	for(unsigned int i = 0; i < pSampleLength; i++) {
		switch(pIoStreamInfo.mSamplesWidth) {
			case NAudioFormats::S8:
				pSamplesOut[i] = pCodec->convertFromS8ToDouble(*static_cast<int8_t*>(pSamplesIn));
				break;

			case NAudioFormats::U8:
				pSamplesOut[i] = pCodec->convertFromU8ToDouble(*static_cast<uint8_t*>(pSamplesIn));
				break;

			case NAudioFormats::S16LE: {
				int16_t lSample(*static_cast<int16_t*>(pSamplesIn));
#				if __BYTE_ORDER == __BIG_ENDIAN
					lSample = switchEndian(lSample);
#				endif
				pSamplesOut[i] = pCodec->convertFromS16ToDouble(lSample);
				break;
			}

			case NAudioFormats::S16BE: {
				int16_t lSample(*static_cast<int16_t*>(pSamplesIn));
#				if __BYTE_ORDER == __LITTLE_ENDIAN
					lSample = switchEndian(lSample);
#				endif
				pSamplesOut[i] = pCodec->convertFromS16ToDouble(lSample);
				break;
			}

			case NAudioFormats::S16HO:
				pSamplesOut[i] = pCodec->convertFromS16ToDouble(*static_cast<int16_t*>(pSamplesIn));
				break;

			case NAudioFormats::U16LE: {
				uint16_t lSample(*static_cast<uint16_t*>(pSamplesIn));
#				if __BYTE_ORDER == __BIG_ENDIAN
					lSample = switchEndian(lSample);
#				endif
				pSamplesOut[i] = pCodec->convertFromU16ToDouble(lSample);
				break;
			}
			case NAudioFormats::U16BE: {
				uint16_t lSample(*static_cast<uint16_t*>(pSamplesIn));
#				if __BYTE_ORDER == __LITTLE_ENDIAN
					lSample = switchEndian(lSample);
#				endif
				pSamplesOut[i] = pCodec->convertFromU16ToDouble(lSample);
				break;
			}
			case NAudioFormats::U16HO:
				pSamplesOut[i] = pCodec->convertFromU16ToDouble(*static_cast<uint16_t*>(pSamplesIn));
			default:
				printl(LL_ERR, __FILE__, "convertFromIntToDouble failed\n");
				return 0;
		}
		pSamplesIn = static_cast<char*>(pSamplesIn) + lBytePerSample;
	}
	return pSampleLength;
}

unsigned int CSamplesPreprocessor::convertFromDoubleToInt
(
		double* pSamplesIn,
		void* pSamplesOut,
		unsigned int pSampleLength,
		NAudioFormats::SStreamInfo &pIoStreamInfo,
		ICodec* pCodec
)
{
	unsigned int lBytePerSample = getBytePerSample(pIoStreamInfo.mSamplesWidth);

	if(!pSamplesIn || !pSamplesOut || !pCodec || !lBytePerSample) {
		return 0;
	}
	if(pIoStreamInfo.mCodec != pCodec->getCodecType()) {
		printl(LL_ERR, __FILE__, "convertFromDoubleToInt: wrong codec.\n");
		return 0;
	}
	for(unsigned int i = 0; i < pSampleLength; i++) {
		switch(pIoStreamInfo.mSamplesWidth) {
			case NAudioFormats::S8:
				*static_cast<int8_t*>(pSamplesOut) = pCodec->convertFromDoubleToS8(pSamplesIn[i]);
				break;

			case NAudioFormats::U8:
				*static_cast<uint8_t*>(pSamplesOut) = pCodec->convertFromDoubleToU8(pSamplesIn[i]);
				break;

			case NAudioFormats::S16LE:
#				if __BYTE_ORDER == __BIG_ENDIAN
					*static_cast<int16_t*>(pSamplesOut) = switchEndian(
							pCodec->convertFromDoubleToS16(pSamplesIn[i]));
#				else
					*static_cast<int16_t*>(pSamplesOut) = pCodec->convertFromDoubleToS16(pSamplesIn[i]);
#				endif
				break;

			case NAudioFormats::S16BE:
#				if __BYTE_ORDER == __LITTLE_ENDIAN
					*static_cast<int16_t*>(pSamplesOut) = switchEndian(
							pCodec->convertFromDoubleToS16(pSamplesIn[i]));
#				else
					*static_cast<int16_t*>(pSamplesOut) = pCodec->convertFromDoubleToS16(pSamplesIn[i]);
#				endif
				break;

			case NAudioFormats::S16HO:
				*static_cast<int16_t*>(pSamplesOut) = pCodec->convertFromDoubleToS16(pSamplesIn[i]);
				break;


			case NAudioFormats::U16LE:
#				if __BYTE_ORDER == __BIG_ENDIAN
					*static_cast<uint16_t*>(pSamplesOut) = switchEndian(
							pCodec->convertFromDoubleToU16(pSamplesIn[i]));
#				else
					*static_cast<uint16_t*>(pSamplesOut) = pCodec->convertFromDoubleToU16(pSamplesIn[i]);
#				endif
				break;

			case NAudioFormats::U16BE:
#				if __BYTE_ORDER == __LITTLE_ENDIAN
					*static_cast<uint16_t*>(pSamplesOut) = switchEndian(
							pCodec->convertFromDoubleToU16(pSamplesIn[i]));
#				else
					*static_cast<uint16_t*>(pSamplesOut) = pCodec->convertFromDoubleToU16(pSamplesIn[i]);
#				endif
				break;

			case NAudioFormats::U16HO:
				*static_cast<uint16_t*>(pSamplesOut) = pCodec->convertFromDoubleToU16(pSamplesIn[i]);
				break;

			default:
				printl(LL_ERR, __FILE__, "convertFromDoubleToInt failed\n");
				return 0;
		}
		pSamplesOut = static_cast<char*>(pSamplesOut) + lBytePerSample;
	}
	return pSampleLength;
}

unsigned int CSamplesPreprocessor::getBytePerSample(NAudioFormats::ESamplesWidth pSamplesWidth)
{
	switch(pSamplesWidth) {
		case NAudioFormats::UNKNOWN_WIDTH:
			printl(LL_ERR, __FILE__, "Unknown sample width. (0)\n");
			return 0;
		case NAudioFormats::S8:
		case NAudioFormats::U8:
			return 1;
		case NAudioFormats::S16LE:
		case NAudioFormats::S16BE:
		case NAudioFormats::S16HO:
		case NAudioFormats::U16LE:
		case NAudioFormats::U16BE:
		case NAudioFormats::U16HO:
			return 2;
		case NAudioFormats::DOUBLE:
			printl(LL_WARN, __FILE__, "Sample width for double is used.\n");
			return (sizeof(double) + 7) / 8;
	}
	printl(LL_ERR, __FILE__, "Unsupported sample width.\n");
	return 0;
}

ICodec* CSamplesPreprocessor::newCodecPerCodecInfo(NAudioFormats::ECodec pCodecInfo)
{
	switch(pCodecInfo) {
		case NAudioFormats::PCM:
			return new CPcmCodec();
		case NAudioFormats::ALAW:
			return new CAlawCodec();
		case NAudioFormats::ULAW:
			return new CUlawCodec();
		case NAudioFormats::UNKNOWN_CODEC:
			printl(LL_ERR, __FILE__, "No codec is set.\n");
			return NULL;
	}
	return NULL;
}

int CSamplesPreprocessor::embedOrExtract
(
		void* pSamples,
		unsigned int pSampleLength,
		unsigned int pLeak,
		unsigned int pInBuffer,
		bool pEmbedding
)
{
	if(!mBytePerSample) {
		printl(LL_ERR, __FILE__, "Byte per sample can't be 0.\n");
		return -1;
	}
	if(!mEmbeddingEngine) {
		printl(LL_ERR, __FILE__, "No embedding engine available.\n");
		return -1;
	}
	if (pSampleLength != static_cast<unsigned int>(mEmbeddingEngine->getNumRequiredSamples())) {
		// TODO support for 0?
		return -1;
	}

	int rv(-1);
	if(mEmbeddingEngine->getSamplesFormat() == NAudioFormats::MODIFIED) {
		// embedding engine need samples as doubles
		//printl(0, __FILE__, "Double samples are used\n");

		double* lDoubleSamples = getDoubleBuffer(pSampleLength);
		if(convertFromIntToDouble(pSamples,
				lDoubleSamples,
				pSampleLength,
				mIoStreamInfo,
				mCodec) != pSampleLength) {
			return -1;
		}
		
		if(pEmbedding) {
			rv = mEmbeddingEngine->embedIntoSamples(lDoubleSamples, pSampleLength, pLeak);
			if(rv < 0)
			{
				printl(LL_ERR, __FILE__, "Embed failed. SamplesFormat: %d(MODIFIED) \n",
						NAudioFormats::MODIFIED);
			}
			addSamplesAmps(mAmpEmbed, mAmpEmbedCount, lDoubleSamples, pSampleLength, true);
		}
		else {
			rv = mEmbeddingEngine->extractFromSamples(lDoubleSamples, pSampleLength, pLeak, pInBuffer);
			if(rv < 0)
			{
				printl(LL_ERR, __FILE__, "Extract failed. SamplesFormat: %d(MODIFIED) \n",
						NAudioFormats::MODIFIED);

			}
			addSamplesAmps(mAmpExtract, mAmpExtractCount, lDoubleSamples, pSampleLength, false);
		}
		if(rv < 0) {
			return -1;
		}
		if(pEmbedding && rv == 0) { // for embedding
			return 0;
		}
		
		if(convertFromDoubleToInt(lDoubleSamples,
				pSamples,
				pSampleLength,
				mIoStreamInfo,
				mCodec) != pSampleLength) {
			return -1;
		}
	}
	else {
		if(pEmbedding) {
			switch(mIoStreamInfo.mSamplesWidth) {
				case NAudioFormats::UNKNOWN_WIDTH:
					printl(LL_ERR, __FILE__, "Unknown sample width. (1)\n");
					return -1;
				case NAudioFormats::S8:
					rv = mEmbeddingEngine->embedIntoSamples(static_cast<int8_t*>(pSamples), pSampleLength, 0);
					break;
				case NAudioFormats::U8:
					rv = mEmbeddingEngine->embedIntoSamples(static_cast<uint8_t*>(pSamples), pSampleLength, 0);
					break;
				case NAudioFormats::S16LE:
#						if __BYTE_ORDER == __LITTLE_ENDIAN
						rv = mEmbeddingEngine->embedIntoSamples(static_cast<int16_t*>(pSamples), pSampleLength, 0);
#						elif __BYTE_ORDER == __BIG_ENDIAN
					{
						int16_t* lSamples(get16BitSampleBuffer(pSampleLength));
						switchEndianFrom16BitBuffer(lSamples,
								static_cast<int16_t*>(pSamples),
								pSampleLength);
						rv = mEmbeddingEngine->embedIntoSamples(lSamples, pSampleLength, 0);
						switchEndianFrom16BitBuffer(static_cast<int16_t*>(pSamples),
								lSamples,
								pSampleLength);
					}
#						else
						printl(LL_ERR, __FILE__, "No little and big endian?\n");
						return -1;
#						endif
					break;
				case NAudioFormats::S16BE:
#						if __BYTE_ORDER == __BIG_ENDIAN
						rv = mEmbeddingEngine->embedIntoSamples(static_cast<int16_t*>(pSamples), pSampleLength, 0);
#						elif __BYTE_ORDER == __LITTLE_ENDIAN
					{
						int16_t* lSamples(get16BitSampleBuffer(pSampleLength));
						switchEndianFrom16BitBuffer(lSamples,
								static_cast<int16_t*>(pSamples),
								pSampleLength);
						rv = mEmbeddingEngine->embedIntoSamples(lSamples, pSampleLength, 0);
						switchEndianFrom16BitBuffer(static_cast<int16_t*>(pSamples),
								lSamples,
								pSampleLength);
					}
#						else
						printl(LL_ERR, __FILE__, "No little and big endian?\n");
						return -1;
#						endif
					break;
				case NAudioFormats::S16HO:
					rv = mEmbeddingEngine->embedIntoSamples(static_cast<int16_t*>(pSamples), pSampleLength, 0);
					break;
				case NAudioFormats::U16LE:
#						if __BYTE_ORDER == __LITTLE_ENDIAN
						rv = mEmbeddingEngine->embedIntoSamples(static_cast<uint16_t*>(pSamples), pSampleLength, 0);
#						elif __BYTE_ORDER == __BIG_ENDIAN
					{
						uint16_t* lSamples(reinterpret_cast<uint16_t*>(get16BitSampleBuffer(pSampleLength)));
						switchEndianFrom16BitBuffer(lSamples,
								static_cast<uint16_t*>(pSamples),
								pSampleLength);
						rv = mEmbeddingEngine->embedIntoSamples(lSamples, pSampleLength, 0);
						switchEndianFrom16BitBuffer(static_cast<uint16_t*>(pSamples),
								lSamples,
								pSampleLength);
					}
#						else
						printl(LL_ERR, __FILE__, "No little and big endian?\n");
						return -1;
#						endif
					break;
				case NAudioFormats::U16BE:
#						if __BYTE_ORDER == __BIG_ENDIAN
						rv = mEmbeddingEngine->embedIntoSamples(static_cast<uint16_t*>(pSamples), pSampleLength, 0);
#						elif __BYTE_ORDER == __LITTLE_ENDIAN
					{
						uint16_t* lSamples(reinterpret_cast<uint16_t*>(get16BitSampleBuffer(pSampleLength)));
						switchEndianFrom16BitBuffer(lSamples,
								static_cast<uint16_t*>(pSamples),
								pSampleLength);
						rv = mEmbeddingEngine->embedIntoSamples(lSamples, pSampleLength, 0);
						switchEndianFrom16BitBuffer(static_cast<uint16_t*>(pSamples),
								lSamples,
								pSampleLength);
					}
#						else
						printl(LL_ERR, __FILE__, "No little and big endian?\n");
						return -1;
#						endif
					break;
				case NAudioFormats::U16HO:
					rv = mEmbeddingEngine->embedIntoSamples(static_cast<uint16_t*>(pSamples), pSampleLength, 0);
					break;
				case NAudioFormats::DOUBLE:
					rv = mEmbeddingEngine->embedIntoSamples(static_cast<double*>(pSamples), pSampleLength, 0);
					break;
			}
		}
		else {
			switch(mIoStreamInfo.mSamplesWidth) {
				case NAudioFormats::UNKNOWN_WIDTH:
					printl(LL_ERR, __FILE__, "Unknown sample width. (2)\n");
					return -1;
				case NAudioFormats::S8:
					rv = mEmbeddingEngine->extractFromSamples(static_cast<int8_t*>(pSamples), pSampleLength, 0, pInBuffer);
					break;
				case NAudioFormats::U8:
					rv = mEmbeddingEngine->extractFromSamples(static_cast<uint8_t*>(pSamples), pSampleLength, 0, pInBuffer);
					break;
				case NAudioFormats::S16LE:
#						if __BYTE_ORDER == __LITTLE_ENDIAN
						rv = mEmbeddingEngine->extractFromSamples(static_cast<int16_t*>(pSamples), pSampleLength, 0, pInBuffer);
#						elif __BYTE_ORDER == __BIG_ENDIAN
					{
						int16_t* lSamples(get16BitSampleBuffer(pSampleLength));
						switchEndianFrom16BitBuffer(lSamples,
								static_cast<int16_t*>(pSamples),
								pSampleLength);
						rv = mEmbeddingEngine->extractFromSamples(lSamples, pSampleLength, 0, pInBuffer);
						switchEndianFrom16BitBuffer(static_cast<int16_t*>(pSamples),
								lSamples,
								pSampleLength);
					}
#						else
						printl(LL_ERR, __FILE__, "No little and big endian?\n");
						return -1;
#						endif
					break;
				case NAudioFormats::S16BE:
#						if __BYTE_ORDER == __BIG_ENDIAN
						rv = mEmbeddingEngine->extractFromSamples(static_cast<int16_t*>(pSamples), pSampleLength, 0, pInBuffer);
#						elif __BYTE_ORDER == __LITTLE_ENDIAN
					{
						int16_t* lSamples(get16BitSampleBuffer(pSampleLength));
						switchEndianFrom16BitBuffer(lSamples,
								static_cast<int16_t*>(pSamples),
								pSampleLength);
						rv = mEmbeddingEngine->extractFromSamples(lSamples, pSampleLength, 0, pInBuffer);
						switchEndianFrom16BitBuffer(static_cast<int16_t*>(pSamples),
								lSamples,
								pSampleLength);
					}
#						else
						printl(LL_ERR, __FILE__, "No little and big endian?\n");
						return -1;
#						endif
					break;
				case NAudioFormats::S16HO:
					rv = mEmbeddingEngine->extractFromSamples(static_cast<int16_t*>(pSamples), pSampleLength, 0, pInBuffer);
					break;
				case NAudioFormats::U16LE:
#						if __BYTE_ORDER == __LITTLE_ENDIAN
						rv = mEmbeddingEngine->extractFromSamples(static_cast<uint16_t*>(pSamples), pSampleLength, 0, pInBuffer);
#						elif __BYTE_ORDER == __BIG_ENDIAN
					{
						uint16_t* lSamples(reinterpret_cast<uint16_t*>(get16BitSampleBuffer(pSampleLength)));
						switchEndianFrom16BitBuffer(lSamples,
								static_cast<uint16_t*>(pSamples),
								pSampleLength);
						rv = mEmbeddingEngine->extractFromSamples(lSamples, pSampleLength, 0, pInBuffer);
						switchEndianFrom16BitBuffer(static_cast<uint16_t*>(pSamples),
								lSamples,
								pSampleLength);
					}
#						else
						printl(LL_ERR, __FILE__, "No little and big endian?\n");
						return -1;
#						endif
					break;
				case NAudioFormats::U16BE:
#						if __BYTE_ORDER == __BIG_ENDIAN
						rv = mEmbeddingEngine->extractFromSamples(static_cast<uint16_t*>(pSamples), pSampleLength, 0, pInBuffer);
#						elif __BYTE_ORDER == __LITTLE_ENDIAN
					{
						uint16_t* lSamples(reinterpret_cast<uint16_t*>(get16BitSampleBuffer(pSampleLength)));
						switchEndianFrom16BitBuffer(lSamples,
								static_cast<uint16_t*>(pSamples),
								pSampleLength);
						rv = mEmbeddingEngine->extractFromSamples(lSamples, pSampleLength, 0, pInBuffer);
						switchEndianFrom16BitBuffer(static_cast<uint16_t*>(pSamples),
								lSamples,
								pSampleLength);
					}
#						else
						printl(LL_ERR, __FILE__, "No little and big endian?\n");
						return -1;
#						endif
					break;
				case NAudioFormats::U16HO:
					rv = mEmbeddingEngine->extractFromSamples(static_cast<uint16_t*>(pSamples), pSampleLength, 0, pInBuffer);
					break;
				case NAudioFormats::DOUBLE:
					rv = mEmbeddingEngine->extractFromSamples(static_cast<double*>(pSamples), pSampleLength, 0, pInBuffer);
					break;
			}
		}

		if(rv < 0) {
			if(pEmbedding)
			{
				printl(LL_ERR, __FILE__, "Embedding failed. Expected SamplesFormat: %d \n",
					mEmbeddingEngine->getSamplesFormat());
			}
			else
			{
				printl(LL_ERR, __FILE__, "Extracting failed. Expected SamplesFormat: %d \n",
					mEmbeddingEngine->getSamplesFormat());
			}
		}
	}

	return rv;
}

int16_t* CSamplesPreprocessor::get16BitSampleBuffer(int pSampleLength)
{
	if((!m16BitSampleBuf && m16BitSampleCount > 0)
	|| (m16BitSampleBuf && m16BitSampleCount <= 0)) {
		printl(LL_ERR, __FILE__, "Inconsistent buffer for 16 bit samples\n");
		return NULL;
	}
	if(pSampleLength <= 0) {
		if(m16BitSampleBuf) {
			delete [] m16BitSampleBuf;
			m16BitSampleBuf = NULL;
			m16BitSampleCount = 0;
		}
		return NULL;
	}

	// check if current buffer is too small
	if(pSampleLength > m16BitSampleCount) {
		delete [] m16BitSampleBuf;
		m16BitSampleBuf = new int16_t [pSampleLength];
		m16BitSampleCount = pSampleLength;
	}
	return m16BitSampleBuf;
}

double* CSamplesPreprocessor::getDoubleBuffer(int pSampleLength)
{
	if((!mDoubleSampleBuf && mDoubleSampleCount > 0)
	|| (mDoubleSampleBuf && mDoubleSampleCount <= 0)) {
		printl(LL_ERR, __FILE__, "Inconsistent buffer for double samples\n");
		return NULL;
	}
	if(pSampleLength <= 0) {
		if(mDoubleSampleBuf) {
			delete [] mDoubleSampleBuf;
			mDoubleSampleBuf = NULL;
			mDoubleSampleCount = 0;
		}
		return NULL;
	}

	// check if current buffer is too small
	if(pSampleLength > mDoubleSampleCount) {
		delete [] mDoubleSampleBuf;
		mDoubleSampleBuf = new double [pSampleLength];
		mDoubleSampleCount = pSampleLength;
	}
	return mDoubleSampleBuf;
}

double* CSamplesPreprocessor::getConvertDoubleBuffer(int pSampleLength)
{
	if((!mConvertDoubleSampleBuf && mConvertDoubleSampleCount > 0)
	|| (mConvertDoubleSampleBuf && mConvertDoubleSampleCount <= 0)) {
		printl(LL_ERR, __FILE__, "Inconsistent buffer for double samples\n");
		return NULL;
	}
	if(pSampleLength <= 0) {
		if(mConvertDoubleSampleBuf) {
			delete [] mConvertDoubleSampleBuf;
			mConvertDoubleSampleBuf = NULL;
			mConvertDoubleSampleCount = 0;
		}
		return NULL;
	}

	// check if current buffer is too small
	if(pSampleLength > mConvertDoubleSampleCount) {
		delete [] mConvertDoubleSampleBuf;
		mConvertDoubleSampleBuf = new double [pSampleLength];
		mConvertDoubleSampleCount = pSampleLength;
	}
	return mConvertDoubleSampleBuf;
}

int16_t CSamplesPreprocessor::switchEndian(int16_t pValue)
{
	char *lBufIn(reinterpret_cast<char*>(&pValue));
	int16_t lRetValue;
	char *lBufOut(reinterpret_cast<char*>(&lRetValue));
	lBufOut[0] = lBufIn[1];
	lBufOut[1] = lBufIn[0];
	return lRetValue;
}

uint16_t CSamplesPreprocessor::switchEndian(uint16_t pValue)
{
	return switchEndian(static_cast<int16_t>(pValue));
}

bool CSamplesPreprocessor::switchEndianFrom16BitBuffer
(
		int16_t *pOutSamples,
		int16_t *pInSamples,
		int pSampleLength
)
{
	if(!pOutSamples || !pInSamples || pSampleLength <= 0) {
		return false;
	}
	for(int i = 0; i < pSampleLength; i++) {
		pOutSamples[i] = switchEndian(pInSamples[i]);
	}
	return true;
}

bool CSamplesPreprocessor::switchEndianFrom16BitBuffer
(
		uint16_t *pOutSamples,
		uint16_t *pInSamples,
		int pSampleLength
)
{
	return switchEndianFrom16BitBuffer(reinterpret_cast<int16_t*>(pOutSamples),
			reinterpret_cast<int16_t*>(pInSamples),
			pSampleLength);
}

void CSamplesPreprocessor::addSamplesAmps(double& pAmp, unsigned int& pAmpCount, double* pSamples, unsigned int pSampleCount, bool pEmbedding)
{
	for (unsigned int i = 0; i < pSampleCount; i++) {
		double lSample((pSamples[i] < 0.0) ? -pSamples[i] : pSamples[i]);
		if (pAmp < lSample) {
			pAmp = lSample;
		}
		//pAmp += (pSamples[i] < 0.0) ? (pSamples[i] * -1.0) : pSamples[i];
		pAmpCount++;
	}

	if (pAmpCount > 20000) {
		//pAmp /= pAmpCount;

		unsigned char lAmp = static_cast<unsigned char>(pAmp * 255.0 + 0.5);

		if (pEmbedding) {
			CUISocket::getInstance()->sendAmpEmbed(lAmp);
		} else {
			CUISocket::getInstance()->sendAmpExtract(lAmp);
		}

		pAmp = 0.0;
		pAmpCount = 0;
	}
}

void* CSamplesPreprocessor::extractThread(void* pThis)
{
	if (!pThis) {
		return NULL;
	}

	return reinterpret_cast<CSamplesPreprocessor*>(pThis)->extractThread();
}

void* CSamplesPreprocessor::extractThread()
{
	int lLeak(0);
	for (;;) {
		pthread_mutex_lock(&mExtractThread.mSync);

		// remove leak
		if (lLeak > 0) {
			printl(LL_WARN, __FILE__, "Leak of %d bytes", lLeak);
			while (mExtractThread.mRingBuffer.len() < static_cast<unsigned int>(lLeak)) {
				pthread_cond_wait(&mExtractThread.mAvail, &mExtractThread.mSync);

				if (mExtractThread.mExit != CExtractThread::EXIT_STATE_NONE) {
					pthread_mutex_unlock(&mExtractThread.mSync);
					return NULL;
				}
			}
			mExtractThread.mRingBuffer.clean(lLeak);
		}

		if (mExtractThread.mExit == CExtractThread::EXIT_STATE_FORCED ||
				mExtractThread.mRingBuffer.len() < mExtractThread.mEngineBufferSize) {
			if (mExtractThread.mExit != CExtractThread::EXIT_STATE_NONE) {
				pthread_mutex_unlock(&mExtractThread.mSync);
				return NULL;
			}

			while (mExtractThread.mRingBuffer.len() < mExtractThread.mEngineBufferSize) {
				pthread_cond_wait(&mExtractThread.mAvail, &mExtractThread.mSync);

				if (mExtractThread.mExit != CExtractThread::EXIT_STATE_NONE) {
					pthread_mutex_unlock(&mExtractThread.mSync);
					return NULL;
				}
			}
		}

		if (mExtractThread.mRingBuffer.get(mExtractThread.mEngineBuffer,
					mExtractThread.mEngineBufferSize) != mExtractThread.mEngineBufferSize) {
			printl(LL_ERR, __FILE__, "Got less bytes from buffer than requested");
		}

		unsigned int lRingBufferSamples(mExtractThread.mRingBuffer.len() / mBytePerSample);
		pthread_mutex_unlock(&mExtractThread.mSync);

		lLeak = embedOrExtract(mExtractThread.mEngineBuffer, mExtractThread.mEngineBufferSize/mBytePerSample, lLeak/mBytePerSample, lRingBufferSamples, false);
		if (lLeak < 0) {
			printl(LL_ERR, __FILE__, "Extract failed, exiting thread");
			return NULL;
		}
		lLeak *= mBytePerSample;
	}

	return NULL;
}

